package cn.admin.remoting.impl;


import cn.admin.remoting.Client;
import cn.admin.remoting.ClientManager;
import cn.admin.remoting.IOEventListener;
import cn.admin.remoting.RemotingException;
import cn.admin.remoting.util.*;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;

import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;

class RepositoryManager {

    private final ConnectionFactoryFactory factoryFactory = new ConnectionFactoryFactory();
    private final SingleClientFutureRepository singleClientFutureRepo =
            new SingleClientFutureRepository();
    private final ClientPoolRepository clientPoolRepo = new ClientPoolRepository();

    ClientConnectionFactory getConnectionFactory(String appName) {
        while (true) {
            try {
                return factoryFactory.getResource(appName,null);
            } catch (Exception ignored) {

            }
        }
    }

    Map<String,ClientConnectionFactory> clientFactorySnapshot() throws ExecutionException,
            InterruptedException {
        return this.factoryFactory.resourcesSnapshot();
    }

    ClientManager.ClientFuture getSingleClientAsync(String singleClientKey,
                                                    List<IOEventListener> listeners) {
        while (true) {
            try {
                return this.singleClientFutureRepo.getResource(singleClientKey,listeners);
            } catch (InterruptedException ignored) {

            } catch (ExecutionException e) {
                return new FailedClientFuture("",new RemotingException(e.getMessage(),e.getCause()));
            } catch (ResourceInvalidException e) {
                return new FailedClientFuture("", (RemotingException) e.getCause());
            }
        }
    }

    String getClientPoolKey(String appName,String url) {
        return this.clientPoolRepo.getPoolKey(appName,url);
    }

    GenericObjectPool<Object> getClientPool(String clientPoolKey,List<IOEventListener> listeners)
            throws ResourceInvalidException, InterruptedException, ExecutionException {
        return this.clientPoolRepo.getResource(clientPoolKey,listeners);
    }

    class PoolableClientFactory implements PoolableObjectFactory<Object> {

        private final String url;
        private final String appName;
        private final List<IOEventListener> listeners;
        private ClientConnectionFactory factory;

        PoolableClientFactory(String url,String appName,List<IOEventListener> listeners) {
            this.url = url;
            this.appName = appName;
            this.listeners = listeners;
            try {
                this.factory = RepositoryManager.this.getConnectionFactory(appName);
            } catch (Exception ignored) {

            }
        }

        @Override
        public Object makeObject() throws Exception {
            return this.factory.createClient(this.url,null,this.listeners);
        }

        @Override
        public void destroyObject(Object o) throws Exception {
            ((Client)o).destroy("PoolableClientFactory.destroyObject()");
        }

        @Override
        public boolean validateObject(Object o) {
            return null != o && ((Client)o).isConnected();
        }

        @Override
        public void activateObject(Object o) throws Exception {

        }

        @Override
        public void passivateObject(Object o) throws Exception {

        }
    }

    private class ClientPoolRepository extends SharedResourcesInit<GenericObjectPool<Object>> {

        @Override
        protected boolean isSingletonResource() {
            return false;
        }

        @Override
        protected GenericObjectPool<Object> keyedInitResource(String poolKey,
        Object attachment) throws Exception {
            String[] temp = this.parsePoolKey(poolKey);
            String appName = temp[0];
            String url = temp[1];
            ConnectionUrl urlObj = ConnectionUrl.get(url);
            int count = Integer.parseInt(urlObj.getProperty("_CONNECTIONNUM"));
            List<IOEventListener> listeners = UnsafeCast.cast(attachment);
            GenericObjectPool<Object> clientPool =
                    new GenericObjectPool<>(RepositoryManager.this.new PoolableClientFactory(url,
                            appName, listeners));
            clientPool.setTestOnBorrow(true);
            clientPool.setMaxActive(count);
            clientPool.setMaxIdle(-1);
            clientPool.setMinIdle(-1);

            try {
                for(int i = 0; i < count; ++i) {
                    clientPool.addObject();
                }
            } catch (Exception e) {
                LoggerInit.LOGGER.error("预先创建连接池连接失败[" + url + "], " + e.getMessage());
            }

            return clientPool;
        }

        @Override
        protected void validate(GenericObjectPool result) throws ResourceInvalidException {
            if (null == result) {
                throw new ResourceInvalidException(new NullPointerException());
            }
        }

        private String getPoolKey(String appName,String url) {
            return appName + "#" + url;
        }

        private String[] parsePoolKey(String clientKey) {
            return clientKey.split("#");
        }
    }

    private class SingleClientFutureRepository extends SharedResourcesInit<ClientManager.ClientFuture> {

        @Override
        protected boolean isSingletonResource() {
            return false;
        }

        @Override
        protected void validate(ClientManager.ClientFuture f) throws ResourceInvalidException {
            if (f.isDone()) {
                try {
                    if (!f.get(0L).isConnected()) {
                        throw new ResourceInvalidException("连接[" + f.targetUrl() + "]已关闭");
                    }
                } catch (RemotingException e) {
                    throw new ResourceInvalidException(e);
                } catch (InterruptedException ignored) {

                }
            }
        }

        @Override
        protected ClientManager.ClientFuture keyedInitResource(String clientKey,
                                                               Object attachment) throws Exception {
            Object[] tmp = ClientKeyUtil.parseClientKey(clientKey);
            String appName = (String) tmp[0];
            String targetUrl = (String) tmp[1];
            InetSocketAddress localAddress = (InetSocketAddress) tmp[2];
            List<IOEventListener> listeners = UnsafeCast.cast(attachment);
            ClientConnectionFactory factory = RepositoryManager.this.getConnectionFactory(appName);
            return factory.createClientAsync(targetUrl,localAddress,listeners);
        }
    }

    private static class ConnectionFactoryFactory extends SharedResourcesInit<ClientConnectionFactory> {

        @Override
        protected boolean isSingletonResource() {
            return false;
        }

        @Override
        protected void validate(ClientConnectionFactory result) throws ResourceInvalidException {

        }

        @Override
        protected ClientConnectionFactory keyedInitResource(String appName, Object attachment) throws Exception {
            return new ClientConnectionFactory(appName);
        }
    }

}
